/*
    异步缓冲区
    异步写日志和同步写日志的区别就在于，异步写日志不需要业务线程亲自将日志落地到某个具体的方向
    而异步写日志只需要将日志消息放入到缓冲区中，剩下的事情就交给异步线程去完成--->读缓冲区，将日志消息落地
    而这个异步缓冲区内部放的内容不应该是一个日志消息类对象，如果这样的话，会有大量的构造和析构的消耗
    所以，缓冲区内部放入的内容就只需要是一个一个的格式化字符串即可，构造日志消息，等一系列工作都交给异步线程去做

    实现思路：借助双缓冲区，分为输入缓冲区和输出缓冲区，业务线程将字符串push到输入缓冲区中，而异步线程从输出缓冲区中读取字符串
             当输出缓冲区为空，并且输入缓冲区有数据，此时就交换两个缓冲区。
             之所以使用这中实现思路，就是为了避免使用队列而导致空间的频繁申请与释放
             虽然使用循环队列可以提高空间的使用率，但是业务线程和异步线程会同时访问一个队列，会造成线程安全的问题
             所以必须得加锁，这就会导致锁冲突的问题。
        使用双缓冲区，不仅有效解决了空间利用率的问题，还降低了锁冲突的概率，准确来说，是降低了生产者和消费者之前的锁冲突概率
*/

#pragma once
#include <iostream>
#include <vector>
#include <cassert>

namespace logging
{
    const static size_t DEFAULT_BUFFER_SIZE = 10 * 1024 * 1024; // 缓冲区默认大小
    const static size_t THRESHOLD_SIZE = 100 * 1024 * 1024;     // 缓冲区大小小于阈值，翻倍增长 
    const static size_t LINEAR_SIZE = 1024 * 1024;              // 大于阈值，线性增长 
    class buffer
    {
    public:
        buffer() : _buffer(DEFAULT_BUFFER_SIZE), _reader(0), _writer(0) {}

        // 写入操作
        void push(const char *data, size_t len)
        {
            // 1.判断缓冲区大小是否足够 --- 满了/或者剩余空间不足len个长度
            //       解决方法：扩容（用于测试极限性能）； 阻塞（实际落地）
            //if(writeAbleSize() < len) return; // 阻塞
            ensureEnoughSize(len); // 扩容

            // 2.向指定位置写入指定长度
            std::copy(data, data + len, &_buffer[_writer]);

            // 3.移动写位置
            moveWriter(len);
        }

        // 剩余可写大小
        size_t writeAbleSize() { return _buffer.size() - _writer; }

        // 读取操作 --- 不直接读取整个数据，会导致数据拷贝
        // 我们直接读取数据的起始位置和长度即可
        const char* begin() { return &_buffer[_reader]; }
        size_t readAbleSize() { return _writer - _reader; }

        // 移动读指针
        void moveReader(size_t len) 
        {
            assert(len <= readAbleSize());
            _reader += len;
        }

        // 交换缓冲区
        void swap(buffer &b)
        {
            _buffer.swap(b._buffer);
            std::swap(_reader, b._reader);
            std::swap(_writer, b._writer);
        }

        // 重置缓冲区---读写位置
        void reset() { _writer = _reader = 0; }

        // 判断缓冲区是否为空
        bool empty() { return (_reader == _writer); }
    private:
        // 可以在写入操作之后，我们自己在内部移动写位置
        void moveWriter(size_t len)
        {
            assert(_writer + len <= _buffer.size());
            _writer += len;
        }

        // 扩容
        void ensureEnoughSize(size_t len) 
        {
            if(len < writeAbleSize()) return; // 不需要扩容
            size_t new_size = 0;
            if(_buffer.size() < THRESHOLD_SIZE) new_size = 2 * _buffer.size();
            else new_size = _buffer.size() + LINEAR_SIZE;
            _buffer.resize(new_size);
        }
    private:
        std::vector<char> _buffer;
        size_t _reader;
        size_t _writer;
    };
}